package handler

import (
	"fmt"

	ds "gitee.com/shipnet/flogo_edge_channel_data_store"
	ms "gitee.com/shipnet/flogo_metric_store"

	"github.com/project-flogo/core/data/coerce"
	"github.com/project-flogo/core/support/log"

	"github.com/project-flogo/core/activity"
	"github.com/project-flogo/core/data/metadata"
)

func init() {
	_ = activity.Register(&Activity{}, New) //activity.Register(&Activity{}, New) to create instances using factory method 'New'
}

// Activity is an sample Activity that can be used as a base to create a custom activity
type Activity struct {
	logger               log.Logger
	edgeChannels         map[string]EdgeChannel
	edgeChannelDataStore ds.EdgeChannelStore
	metricDataStore      ms.MetricStore
}

var activityMd = activity.ToMetadata(&Settings{}, &Input{}, &Output{})

//New optional factory method, should be used if one activity instance per configuration is desired
func New(ctx activity.InitContext) (activity.Activity, error) {
	s := &Settings{}
	err := metadata.MapToStruct(ctx.Settings(), s, true)
	if err != nil {
		ctx.Logger().Errorf("invalid settings: %+v", err)
		return nil, err
	}

	ctx.Logger().Debugf("Setting: %+v", s)

	msSettings := MetricDataStoreSettings{}
	err = metadata.MapToStruct(s.MetricDataStore, &msSettings, true)
	if err != nil {
		return nil, err
	}
	metricDataStore, err := ms.New(ms.StoreSettings{
		Addr:      msSettings.Addr,
		DB:        msSettings.DB,
		Password:  msSettings.Password,
		KeyPrefix: msSettings.KeyPrefix,
	})
	if err != nil {
		return nil, err
	}

	dsSettings := EdgeChannelDataStoreSettings{}
	err = metadata.MapToStruct(s.EdgeChannelDataStore, &dsSettings, true)
	if err != nil {
		return nil, err
	}

	edgeChannelDataStore, err := ds.New(ds.StoreSettings{
		Addr:      dsSettings.Addr,
		DB:        dsSettings.DB,
		Password:  dsSettings.Password,
		KeyPrefix: dsSettings.KeyPrefix,
	})
	if err != nil {
		return nil, err
	}
	act := &Activity{
		edgeChannelDataStore: edgeChannelDataStore,
		metricDataStore:      metricDataStore,
	}
	act.logger = ctx.Logger()
	// act.actorContext = actor.EmptyRootContext
	act.edgeChannels = map[string]EdgeChannel{}

	return act, nil
}

// Metadata returns the activity's metadata
func (a *Activity) Metadata() *activity.Metadata {
	return activityMd
}

// Eval implements api.Activity.Eval - Logs the Message
func (a *Activity) Eval(ctx activity.Context) (done bool, err error) {

	input := &Input{}
	err = ctx.GetInputObject(input)
	if err != nil {
		return true, err
	}

	ctx.Logger().Debugf("Input: %+v", input)
	switch input.Command {
	case "INIT":
		{
			return a.evalInit(input, ctx)
		}
	case "CREATE":
		{
			return a.evalCreate(input, ctx)
		}
	case "DELETE":
		{
			return a.evalDelete(input, ctx)
		}
	case "QUERY":
		{
			return a.evalQuery(input, ctx)
		}
	default:
		{
			return true, fmt.Errorf("invalid command: %s", input.Command)
		}
	}
}

type edgeChannelInitParams struct {
	Channels []EdgeChannelParams `md:"channels"`
}

type edgeChannelDeleteParams struct {
	EdgeChannelID string
}

type edgeChannelQueryParams struct {
}

func extractInitParams(maps map[string]interface{}) (*edgeChannelInitParams, error) {
	tmparams := struct {
		Channels []interface{} `md:"channels"`
	}{}
	err := metadata.MapToStruct(maps, &tmparams, true)
	if err != nil {
		return nil, err
	}
	params := edgeChannelInitParams{}
	for _, ch := range tmparams.Channels {
		v, err := coerce.ToObject(ch)
		if err != nil {
			return nil, err
		}
		channel, err := mapToEdgeChannelParams(v)
		if err != nil {
			return nil, err
		}
		params.Channels = append(params.Channels, channel)
	}
	return &params, nil
}

func (a *Activity) evalInit(input *Input, ctx activity.Context) (bool, error) {
	params, err := extractInitParams(input.Params)
	if err != nil {
		return true, err
	}
	for _, ch := range params.Channels {
		err := a.createEdgeChannel(ch, ctx.Logger())
		if err != nil {
			return true, err
		}
	}
	for k := range a.edgeChannels {
		if !contains(params.Channels, k) {
			ch := edgeChannelDeleteParams{
				EdgeChannelID: k,
			}
			err := a.deleteEdgeChannel(ch)
			if err != nil {
				return true, err
			}
		}
	}
	states := a.getEdgeChannelsState()
	output := &Output{
		Result: Result{
			Command:  input.Command,
			Params:   input.Params,
			Channels: states,
		},
	}
	err = ctx.SetOutputObject(output)
	return true, err
}

func extractCreateParams(maps map[string]interface{}) (*EdgeChannelParams, error) {
	params, err := mapToEdgeChannelParams(maps)
	return &params, err
}

func (a *Activity) evalCreate(input *Input, ctx activity.Context) (bool, error) {
	params, err := extractCreateParams(input.Params)
	if err != nil {
		return true, err
	}
	err = a.createEdgeChannel(*params, ctx.Logger())
	if err != nil {
		return true, err
	}
	states := a.getEdgeChannelsState()
	output := &Output{
		Result: Result{
			Command:  input.Command,
			Params:   input.Params,
			Channels: states,
		},
	}
	err = ctx.SetOutputObject(output)
	return true, err
}

func (a *Activity) evalDelete(input *Input, ctx activity.Context) (bool, error) {
	params := edgeChannelDeleteParams{}
	err := metadata.MapToStruct(input.Params, &params, true)
	if err != nil {
		return true, err
	}
	err = a.deleteEdgeChannel(params)
	states := a.getEdgeChannelsState()
	output := &Output{
		Result: Result{
			Command:  input.Command,
			Params:   input.Params,
			Err:      err.Error(),
			Channels: states,
		},
	}
	err = ctx.SetOutputObject(output)
	return true, err
}

func (a *Activity) evalQuery(input *Input, ctx activity.Context) (bool, error) {
	params := edgeChannelQueryParams{}
	err := metadata.MapToStruct(input.Params, &params, true)
	if err != nil {
		return true, err
	}
	states := a.getEdgeChannelsState()
	output := &Output{
		Result: Result{
			Command:  input.Command,
			Params:   input.Params,
			Channels: states,
		},
	}
	err = ctx.SetOutputObject(output)
	return true, err
}

func (a *Activity) getEdgeChannelsState() map[string]EdgeChannelState {
	states := map[string]EdgeChannelState{}
	for k, v := range a.edgeChannels {
		states[k] = v.GetState()
	}
	return states
}

func contains(channels []EdgeChannelParams, ch string) bool {
	for _, p := range channels {
		if p.EdgeChannelID == ch {
			return true
		}
	}
	return false
}
